package com.k8.rpc.invoke.invoker;

import com.k8.rpc.invoke.Invoker;
import com.k8.rpc.invoke.invocation.Invocation;
import com.k8.rpc.result.Result;
import com.k8.rpc.result.RpcResult;
import com.k8.rpc.url.URL;

import java.lang.reflect.Method;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import static com.k8.common.constants.CommonKeyConstants.IO_TIMEOUT_KEY;

/**
 * @Author: k8
 * @CreateTime: 2025-03-26
 * @Version: 1.0
 */
public class ProviderInvoker<T> implements Invoker<T> {
    private URL url;
    private Class<T> interfaceClazz;
    private volatile boolean available = true;
    private T target;
    private static int DEFAULT_IO_TIME_OUT = 3000;
    protected final int timeOut;

    public ProviderInvoker(URL url, Class<T> interfaceClazz, T target) {
        this.url = url;
        assert interfaceClazz != null;
        assert target != null;
        this.interfaceClazz = interfaceClazz;
        this.target = target;
        this.timeOut = url.getParameter(IO_TIMEOUT_KEY, DEFAULT_IO_TIME_OUT);
    }

    @Override
    public URL getUrl() {
        return this.url;
    }

    @Override
    public boolean isAvailable() {
        return this.available;
    }

    @Override
    public void destroy() {

    }

    @Override
    public Class<T> getInterface() {
        return this.interfaceClazz;
    }

    /**
     * provider的Invoker应该全部提供AsyncResult
     * consumer的Invoker应该有异步转同步功能，阻塞等待provider返回的AsyncResult的Future返回值
     */
    @Override
    public Result<Object> invoke(Invocation invocation) throws Throwable {
        String methodName = invocation.getMethodName();
        Class<?> targetClass = this.target.getClass();
        Result<Object> result = null;
        try {
            Method method = targetClass.getMethod(methodName, invocation.getParameterTypes());
            Object value = method.invoke(target, invocation.getArguments());
            if (value instanceof Future<?> future) {
                result = new RpcResult<>();
                if (timeOut < 0) {
                    value = future.get();
                } else {
                    value = future.get(timeOut, TimeUnit.MILLISECONDS);
                }
                result.setValue(value);
            } else {
                result = new RpcResult<>();
                result.setValue(value);
            }
        } catch (Throwable throwable) {
            result = new RpcResult<>();
            result.setException(throwable);
        }
        return result;
    }


    @Override
    public T getTarget() {
        return target;
    }
}
